本文为看雪论坛精华文章
看雪论坛作者ID:jishuzhain
笔者目前是初学者,该文章的分析的每一步都是经过实践与参考他人分析报告而输出。分析时间花了三个星期,文中如有错误,欢迎各位指正。
样本概况
文件名称:rdpscan.exe
文件大小:85504 byte
文件类型:PE32 executable for MS Windows (console) Intel 80386 32-bitMD5:d0840aeb2642d718f325a07a4b7f6751
SHA1:ae77ca712670176650b01d30e6fe4801e90fafe2
SHA256:9966826ada8b1f366a9e7b9b1e7c430a2a49dda60eb7025c7481295e3ab7f9e4
文件名称:ssleay32.dll
文件大小:67416 byte
文件类型:dataMD5:27b0374083f46693df15c0e3fffad070SHA1:0eb87babebc32e526f6128fc4be3686c034c4185
SHA256:948e2def7339cf87071913148705bbba3cd9a8279c6157251036d6fac59983d5
拿到文件后,是个压缩包,进行解压后,发现如下文件。
rdpscan.exe 因为之前国内厂商已经检测,并且已经添加特征库,所以在线分析会被检测出。https://www.virustotal.com/gui/file/9966826ada8b1f366a9e7b9b1e7c430a2a49dda60eb7025c7481295e3ab7f9e4/detection
ssleay32.dll(shellcode)由于关键信息数据已加密,静态分析时则无法检测。https://www.virustotal.com/gui/file/948e2def7339cf87071913148705bbba3cd9a8279c6157251036d6fac59983d5/detection
微步云沙箱检测如下,结果是该文件为安全:
这与自己在本地虚拟机运行时一致,当第一次拿到样本时,当晚就在虚拟机里实际运行,直接提示缺失组件,无法成功运行。
因为后门编译为debug版本,没有找到对应的运行库,无法运行,导致沙箱无法对其进行恶意性行为检测。https://s.threatbook.cn/report/file/0b5a6f745a47e2cb83c167e1f53f92db38f21e07a73f8600fc58dbc73cd927a7/?env=win7_sp1_enx64_office2013如果要实际运行,本地需要安装VS2015以上版本后才会自带VC运行库。直接在网上搜索VCRUNTIME140.dll缺失,会找到一些解决文章,但是经过实践,都失败了,无法安装运行库。
最后只能退而求其次,安装了VS2015,因为会自带所需运行库。
附一个视频,安装VCRUNTIME140.dll。还未实验,不知道是否能行。https://www.youtube.com/watch?v=AqxebQEFU64
该rdpscan扫描工具参考了之前网上公布的工具源码,所以按照之前网上的分析步骤,先去源项目的github查下源码。https://github.com/robertdavidgraham/rdpscan/blob/master/src/main.c
经过仔细对比,在471-190行之间的代码被加入了一个判断,如果条件成立就跳转执行恶意代码。
if (cfg.list_filename == NULL && cfg.target_count == 1 && is_ipv4_range(cfg.targets[0])) {
char *string = cfg.targets[0];
unsigned index = 0;
struct Range range;
struct RangeList list = {0};
size_t i;
uint64_t count;
struct BlackRock br;
free(cfg.targets);
cfg.targets = 0;
cfg.target_count = 0;
range = range_parse_ipv4(string, &index, (unsigned)strlen(string));
if (!range_is_valid(range)) {
fprintf(stderr, "[-] %s: invalid range\n", string);
exit(1);
}IDA打开rdpscan.exe翻译成伪代码后如图,goto LABEL_20为跳转:sub_405040为触发函数,执行完后,如果返回值为0,则直接跳转。逻辑是只要在命令行里正常输入IP地址后,一回车执行就会设置返回值为0,接着来到LABEL_20进行分析。if ( !dword_426EE0 )
dword_426EE0 = (int)sub_4050A0();CreateMutex函数的作用是找出当前系统是否已经存在指定进程的实例,如果没有则创建一个互斥体。然后得到调用GetLastError得到的错误码,如果执行成功,就直接进入到如下部分:if ( v24 != 183 )
{
v26 = GetModuleHandleA("KERNEL32.DLL");
v27 = GetProcAddress(v26, "VirtualAlloc");
v28 = GetModuleHandleA("KERNEL32.DLL");
v29 = GetProcAddress(v28, "CreateThread");
v30 = (void *)((int (__stdcall *)(_DWORD, signed int, signed int, signed int))v27)(0, 0x100000, 4096, 64);
if ( sub_405940(v30) )
((void (__stdcall *)(_DWORD, _DWORD, void *, _DWORD, _DWORD, _DWORD))v29)(0, 0, v30, 0, 0, 0);
}先创建一块虚拟内存空间,接着调用CreateThread新建一个线程。这里有个比较奇怪的函数sub_405940,跟进看看。 sub_405940函数的作用是读取ssleay32.dll文件内容,放入之前创建的虚拟内存空间中,之后通过CreateThread新开线程执行,那可以猜测ssleay32.dll为需要执行的后门代码。IDA静态分析与ollydbg动态分析时一致,现在动态分析下,刚分析时需要下的断点。00405D73 |. FFD7 call edi ; kernel32.CreateThread
之后创建了新线程后,该线程就执行了ssleay32.dll内容。shellcode前面是通过FS寄存器找到kernel32.dll基址,之后找到
00C9F45C 75F11222 kernel32.GetProcAddress
00C9F454 75F148D7 kernel32.LoadLibraryA
00C9F450 75F11826 kernel32.VirtualAlloc
这三个函数地址, 此时的基址为0x01F20000,但偏移是不变的。其中用到的混淆方法是通过kernel32.dll基址加偏移获取到API函数名称时,取出每个字符然后经过循环算术处理后最后与先前设定的硬编码的值进行比较。如果正确,那么此时就是需要使用到的API函数,避免了直接使用硬编码的API名称进行比较来获取到需要的API函数。
接着检测了这三个API函数是否存在软件断点(与0xCC进行比较),如果存在断点就直接跳转到01F3074E(此时的基址为0x01F20000,但偏移是不变的)然后退出函数。
调用VirustualAlloc创建一段内存空间,并将一段硬编码的内容(加密的代码指令)复制进来,之后再从其余硬编码内容里复制一段内容作为解密时用到的密钥,最后复制进加密的字符串内容,等待后续解密使用。
之后根据密钥对已复制的内容进行解密,解密完毕后,对密钥部分进行清零。从加密的字符串数据里,解密出KERNEL32.dll,调用LoadLibraryA加载。之后从加密的字符内容里,解密出KERNEL32.dll里需要用到的API函数名称,调用GetProcAddress获取地址。
00C9F43C 00DB9C00 ASCII "HeapFree"
00C9F43C 00DB9C00 ASCII "InitializeCriticalSection"
00C9F428 00DB9C00 ASCII "LocalAlloc"
00C9F428 00DB9C00 ASCII "LocalFree"
00C9F428 00DB9C00 ASCII "SetUnhandledExceptionFilter"
00C9F428 00DB9C00 ASCII "TerminateThread"
00C9F428 00DB9C00 ASCII "GetCurrentThread"
00C9F428 00DB9C00 ASCII "GetCurrentProcessId"
00C9F428 00DB9C00 ASCII "CreateFileMappingA"
00C9F428 00DB9C00 ASCII "MapViewOfFile"
00C9F428 00DB9C00 ASCII "UnmapViewOfFile"
00C9F428 00DB9C00 ASCII "EnterCriticalSection"
00C9F428 00DB9C00 ASCII "LeaveCriticalSection"
...
00C9F43C 00DB9C00 ASCII "ExitProcess"接着通过LoadLibraryA加载解密后的字符串USER32.dll,之后从加密的字符内容里,解密出USER32.dll里需要用到的API函数名称,调用GetProcAddress获取地址。
0211FB30 |02239C00 ASCII "GetForegroundWindow"
0211FB44 02239C00 ASCII "EnumChildWindows"
0211FB30 02239C00 ASCII "wsprintfA"
0211FB30 02239C00 ASCII "EnableWindow"
0211FB30 02239C00 ASCII "GetClassNameA"接着通过LoadLibraryA加载解密后的字符串ADVAPI32.dll。之后从加密的字符内容里,解密出ADVAPI32.dll里需要用到的API函数名称,调用GetProcAddress获取地址。
0211FB30 02239C00 ASCII "RegCreateKeyExW"
0211FB44 02239C00 ASCII "RegQueryValueExW"
0211FB30 02239C00 ASCII "FreeSid"
0211FB44 02239C00 ASCII "SetSecurityDescriptorDacl"
0211FB30 02239C00 ASCII "InitializeSecurityDescriptor"
0211FB30 02239C00 ASCII "SetEntriesInAclW"
0211FB30 02239C00 ASCII "AllocateAndInitializeSid"
0211FB44 02239C00 ASCII "RegOpenKeyExW"
0211FB30 02239C00 ASCII "RegSetValueExW"
0211FB30 02239C00 ASCII "RegDeleteValueW"
0211FB30 02239C00 ASCII "RegNotifyChangeKeyValue"
0211FB30 02239C00 ASCII "RegEnumValueA"
0211FB30 02239C00 ASCII "RegCloseKey"接着通过LoadLibraryA加载解密后的字符串WS2_32.dll。之后从加密的字符内容里,解密出WS2_32.dll里需要用到的API函数名称,调用GetProcAddress获取地址。
前面需要用到的API函数准备完毕后,之后流程进入call edi(0x0212166A,偏移166A)在此之前已经对其进行了解密,跟进后进行分析。edi里只调用了一个函数,偏移12A3,继续跟进。进入12A3后,call 021211C2,内部调用RtlAllocateHeap函数生成了一段堆空间,这里使用了隐藏API函数的方法,如下:后面两个函数判断是否申请成功,申请成功后继续往下执行。call 021212D2 得到当前进程的默认堆的句柄,并申请一段堆空间。call 02121096 解密字符串获取到“memcpy”,获取到msvcrt.dll地址,最后获取到函数memcpy的api地址,将一段地址的0x10200长度内容复制到之前申请的堆空间里。调用WideCharToMultiByte将ascii码转换为Unicode,这里有些函数的作用是对ascii码与Unicode进行互转,中间转换可以不考虑,只关心最后结果就好。运行到这里,则开始使用之前申请的堆空间的地址作为参数来创建第一个线程(偏移1B4F)创建完毕后CloseHandle线程句柄。第二个线程(偏移1C31)跟进后发现会利用CreateFileW函数检查"\\.\Regmon"、"\\.\FileMon"、"\\.\ProcmonDebugLogger"、"\\.\NTICE" 等工具是否存在。如果发现对CreateFile下了0xCC断点或者存在执行后发现以上文件就会调用 eax=75F2D822 (kernel32.TerminateProcess) 退出进程。 之后如果错误码不为0,则调用CloseHandle。进入第三个线程准备中。第三个线程(偏移1E63)后面调用Sleep函数暂时无法跟进线程里,使用WaitForSingleObject函数可以转到第三个线程地址。
接着调用了user32 getforegroundwindow,而GetForegroundWindow获取一个前台窗口的句柄(用户当前工作的窗口),之后使用eax=76900EAC (user32.EnumChildWindows)枚举以下窗口类名,判断是否存在。 0295FE14 0031C3B8 UNICODE "ACPUASM"
0295FE34 0031C3F8 UNICODE "AOPOASM"
0295FE54 00320658 \String2 = "AOPUASM"
0295FE54 00320670 \String2 = "ACPOASM"
0295FE34 0032C060 UNICODE "WinDbgFrameClass"但第四个进程(偏移2062)调用call esi创建的线程跟进后存在反调试,会检测kernelbase.IsDebuggerPresent函数的开头第一个字节是否为0x64。 利用IsDebuggerPresent 和 GetTickCount函数来检测调试器,其中使用GetTickCount获取时间,如果时间相差1秒就退出进程。遇到的第一个难题,在阅读腾讯御见发布的分析报告中,发现截图里的指令,我在使用IDA打开ssleay32.dll中始终无法找到,由于对工具的不熟练,就这么困扰了几天。之后分析发现,对于对于动态加载的shellcode,可以在运行过程中使用LordPE dump指定的内存,然后使用IDA打开进行分析,稍稍发现了一些线索。不过这种方法不太好,最好的方法是在ollydbg中如果运行到需要分析的部分时,右键选择【复制】【复制数据到文件】即可,之后IDA打开选择以32位汇编进行显示就会显示当前实际存在内存中的汇编指令,就这么本地进行分析,这个问题就这么解决了。然后接着开始分析,偏移2171处为第五个线程,通过使用OpenMutex搜索互斥量来判断Wireshark是否存在,存在就终止进程。ASCII"Wireshark-is-running-{9CA78EEA-EA4D-4490-9240-FC01FCEF464B}"
上面这几个线程创建完毕后,来到生成魔法字符串的部分,生成的内容为Poison Ivy C++,同样是从加密的字符串里进行解密,之后进行拼接。 通过搜索发现这是一款非常经典的远控木马特征字符串,之后通过call ebx进入木马下一部分来执行不同功能的shellcode。第一个call 02302A98(偏移2A98),创建了一块堆空间,将之前的ssleay32.dll内容给复制进来,之后EIP指向该处执行。这里相当于执行完call ebx后就又重新执行了之前所有的shellcode内容。前三个call 02302A9B shellcode模块(偏移为2A9B)主要目的是使用之前的shellcode环境准备部分再次新开地址空间来运行,执行另外的恶意行为。后三个call 02302A9B shellcode模块(偏移为2A9B)里则包含了对配置信息的解密读取并创建svchost.exe进程并注入shellcode。call 02302A9B 为相关shellcode模块(偏移为2A9B),这里共6个shellcode子模块。
在这些子模块中要找到木马的配置项,难度有些大,在动态调试中,发现都会创建新线程加载之前的准备环境内容,由于目前动态调试过于复杂,只好虚拟机里实际运行后门看看。安装好火绒安全,主要是使用已变成内置组件的火绒剑。当直接运行起来后,在进程列表里可以查看到新创建了svchost.exe进程。开启监控,命令行一运行rdpscan扫描目标,立马就有了数据被记录。由于之前的分析已经得知ssleay32.dll为shellcode,需要被加载的,所以这里重点关注。看到已经对这个文件进行了打开操作,双击看看具体内容。 发现访问了可疑IP,以及新发现了svchost.exe进程,说明这是后门创建的。 在【进程】选项里找到3516进程ID的进程看看,右键选择【进程信息】。 查看线程,发现被注入了shellcode,同时在运行中。 现在的问题是如何查找该木马的C2域名与地址?因为上面火绒剑仅仅记录了指向的IP。这里的话可以先猜测,比如搜索“http”,但有时不一定能搜到。由于之前已看了相关的分析报告,目前后门已经运行中,所以这里就能确定相关的关键字。很明显,hw.html是可疑的,于是找第一个出现的字符串虚拟地址,在ollydbg附加刚刚发现的svchost.exe成功查看到木马的配置如下:
看雪ID:jishuzhain
https://bbs.pediy.com/user-253374.htm
*本文由看雪论坛 jishuzhain 原创,转载请注明来自看雪社区
活动奖励:
>《智能合约安全分析和审计指南》(王艺桌、陈佳林等著)作者亲笔签名图书一本参与形式:
在本文下方留言,
留言点赞最多的1名小伙伴
即可免费获得一本图书!
注意事项:
1. 点赞活动8月12日 17点截止。微信公众号头条文章尾部,公布获奖名单。3. 严禁用马甲参与活动,一经发现视为放弃参加此活动。
公众号ID:ikanxue
官方微博:看雪安全
商务合作:wsc@kanxue.com